package com.github.byakkili.bim.protocol;

import cn.hutool.core.util.ReflectUtil;
import com.github.byakkili.bim.core.exception.BimException;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.MessageLite;
import com.google.protobuf.util.JsonFormat;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Guannian Li
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ProtoBufHelper {
    private static final boolean HAS_PARSER;
    /**
     * 方法缓存
     */
    private static final Map<Class, Message> INSTANCES = new ConcurrentHashMap<>();
    /**
     * A Printer converts protobuf message to JSON format.
     */
    private static final JsonFormat.Printer PRINTER = JsonFormat.printer().includingDefaultValueFields().omittingInsignificantWhitespace();

    /**
     * 是否支持Protobuf
     *
     * @param clazz Protobuf对象类型
     * @return Boolean
     */
    public static boolean isSupported(Class<?> clazz) {
        return clazz != null && Message.class.isAssignableFrom(clazz);
    }

    /**
     * Protobuf 转 Json
     *
     * @param message Protobuf对象
     * @return Json
     */
    public static String serializeJson(Message message) {
        try {
            return PRINTER.print(message);
        } catch (InvalidProtocolBufferException e) {
            throw new BimException("Protobuf failed to convert JSON.", e);
        }
    }

    /**
     * 是否支持Protobuf
     *
     * @param json  Json字符串
     * @param clazz Protobuf对象类型
     * @param <T>   Protobuf对象类型
     * @return Boolean
     */
    public static <T extends Message> T deserializeJson(String json, Class<T> clazz) {
        try {
            Message prototype = getDefaultInstance(clazz);
            Message.Builder builder = prototype.newBuilderForType();
            JsonFormat.parser().merge(json, builder);
            @SuppressWarnings("unchecked")
            T message = (T) builder.build();
            return message;
        } catch (InvalidProtocolBufferException e) {
            throw new BimException("JSON failed to convert Protobuf.", e);
        }
    }

    /**
     * 序列化
     *
     * @param message Protobuf对象
     * @return 字节
     */
    public static byte[] serialize(Message message) {
        return message == null ? null : message.toByteArray();
    }

    /**
     * 反序列化
     *
     * @param bytes 字节
     * @param clazz Protobuf对象类型
     * @param <T>   Protobuf对象类型
     * @return Protobuf对象
     */
    @SneakyThrows(InvalidProtocolBufferException.class)
    public static <T extends Message> T deserialize(byte[] bytes, Class<T> clazz) {
        MessageLite prototype = getDefaultInstance(clazz);
        if (HAS_PARSER) {
            @SuppressWarnings("unchecked")
            T message = (T) prototype.getParserForType().parseFrom(bytes);
            return message;
        } else {
            @SuppressWarnings("unchecked")
            T message = (T) prototype.newBuilderForType().mergeFrom(bytes).build();
            return message;
        }
    }

    private static Message getDefaultInstance(Class<? extends Message> clazz) {
        Message message = INSTANCES.get(clazz);
        if (message == null) {
            Method instanceMethod = ReflectUtil.getPublicMethod(clazz, "getDefaultInstance");
            message = ReflectUtil.invokeStatic(instanceMethod);
            if (message != null) {
                Message previousValue = INSTANCES.putIfAbsent(clazz, message);
                if (previousValue != null) {
                    message = previousValue;
                }
            }
        }
        return message;
    }

    static {
        boolean hasParser = false;
        try {
            // MessageLite.getParserForType() is not available until protobuf 2.5.0.
            MessageLite.class.getDeclaredMethod("getParserForType");
            hasParser = true;
        } catch (Exception e) {
            // Ignore
        }
        HAS_PARSER = hasParser;
    }
}
